#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <linux/limits.h>
#include <unistd.h>
#include <attr/xattr.h>
#include <errno.h>
#include "fscontroller.h"
#include "fslayer.h"

using namespace std;

class LocalFS : public FSLayer {
	public:
	/* just shortcut for 
	   virtual int getattr(const char* (path), struct stat* (statbuf), PtrList<FSLayer*> *next) 
	   defined in fslayer.h
	*/
	/** Get the node attributes
	   also will be called when root parent is listed
	*/
	LocalFS() {
		this->root_dir = NULL;
	}
	~LocalFS() {
		if(this->root_dir)
			free(this->root_dir);
	}
	FUSE_GETATTR(const char *path, struct stat *mode) {
		int retstat = 0;
		char fpath[PATH_MAX];
		
		this->fullpath(fpath, path);
		
		retstat = lstat(fpath, mode);
		if (retstat != 0)
			retstat = this->controller->error("this->getattr lstat");
		return retstat;
	}
	/** Read the target of a symbolic link
	*/
	FUSE_READLINK(const char *path, char *link, size_t size) {
		int retstat = 0;
		char fpath[PATH_MAX];
		
		this->fullpath(fpath, path);
		
		retstat = readlink(fpath, link, size - 1);
		if (retstat < 0)
			retstat = this->controller->error("this->readlink readlink");
		else  {
			link[retstat] = '\0';
			retstat = 0;
		}
		return(retstat);
    	}
	/** Will be called for all creations
	    of non-directory, non-symlink nodes
	*/
	FUSE_MKNOD(const char *path, mode_t mode, dev_t dev) {
		int retstat = 0;
		char fpath[PATH_MAX];
		
		this->fullpath(fpath, path);
		
		// On Linux this could just be 'mknod(path, mode, rdev)' but this
		//  is more portable
		if (S_ISREG(mode)) {
			retstat = open(fpath, O_CREAT | O_EXCL | O_WRONLY, mode);
			if (retstat < 0)
				retstat = this->controller->error("this->mknod open");
			else {
				retstat = close(retstat);
				if (retstat < 0)
					retstat = this->controller->error("this->mknod close");
			}
		} else
			if (S_ISFIFO(mode)) {
				retstat = mkfifo(fpath, mode);
				if (retstat < 0)
					retstat = this->controller->error("this->mknod mkfifo");
			} else {
				retstat = mknod(fpath, mode, dev);
				if (retstat < 0)
					retstat = this->controller->error("this->mknod mknod");
			}
		
		return retstat;
	}

	/** Create a directory */
	FUSE_MKDIR(const char *path, mode_t mode)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = mkdir(fpath, mode);
	    if (retstat < 0)
		retstat = this->controller->error("this->mkdir mkdir");
	    
	    return retstat;
	}
	/** Remove a file */
	FUSE_UNLINK(const char *path)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = unlink(fpath);
	    if (retstat < 0)
		retstat = this->controller->error("this->unlink unlink");
	    
	    return retstat;
	}
	
	/** Remove a directory */
	FUSE_RMDIR(const char *path)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    
	    retstat = rmdir(fpath);
	    if (retstat < 0)
		retstat = this->controller->error("this->rmdir rmdir");
	    
	    return retstat;
	}
	
	/** Create a symbolic link */
	// The parameters here are a little bit confusing, but do correspond
	// to the symlink() system call.  The 'path' is where the link points,
	// while the 'link' is the link itself.  So we need to leave the path
	// unaltered, but insert the link into the mounted directory.
	FUSE_SYMLINK(const char *path, const char *link)
	{
	    int retstat = 0;
	    char flink[PATH_MAX];
	    
	    this->fullpath(flink, link);
	    
	    retstat = symlink(path, flink);
	    if (retstat < 0)
		retstat = this->controller->error("this->symlink symlink");
	    
	    return retstat;
	}
	
	/** Rename a file */
	// both path and newpath are fs-relative
	FUSE_RENAME(const char *path, const char *newpath)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    char fnewpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    this->fullpath(fnewpath, newpath);
	    
	    retstat = rename(fpath, fnewpath);
	    if (retstat < 0)
		retstat = this->controller->error("this->rename rename");
	    
	    return retstat;
	}
	
	/** Create a hard link to a file */
	FUSE_LINK(const char *path, const char *newpath)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX], fnewpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    this->fullpath(fnewpath, newpath);
	    
	    retstat = link(fpath, fnewpath);
	    if (retstat < 0)
		retstat = this->controller->error("this->link link");
	    
	    return retstat;
	}
	
	/** Change the permission bits of a file */
	FUSE_CHMOD(const char *path, mode_t mode)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = chmod(fpath, mode);
	    if (retstat < 0)
		retstat = this->controller->error("this->chmod chmod");
	    
	    return retstat;
	}
	
	/** Change the owner and group of a file */
	FUSE_CHOWN(const char *path, uid_t uid, gid_t gid)
	  
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = chown(fpath, uid, gid);
	    if (retstat < 0)
		retstat = this->controller->error("this->chown chown");
	    
	    return retstat;
	}
	
	/** Change the size of a file */
	FUSE_TRUNCATE(const char *path, off_t newsize)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = truncate(fpath, newsize);
	    if (retstat < 0)
		this->controller->error("this->truncate truncate");
	    
	    return retstat;
	}
	
	/** Change the access and/or modification times of a file */
	/* note -- I'll want to change this as soon as 2.6 is in debian testing */
	FUSE_UTIME(const char *path, struct utimbuf *ubuf)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = utime(fpath, ubuf);
	    if (retstat < 0)
		retstat = this->controller->error("this->utime utime");
	    
	    return retstat;
	}
	
	/** File open operation
	 *
	 * No creation, or truncation flags (O_CREAT, O_EXCL, O_TRUNC)
	 * will be passed to open().  Open should check if the operation
	 * is permitted for the given flags.  Optionally open may also
	 * return an arbitrary filehandle in the fuse_file_info structure,
	 * which will be passed to all file operations.
	 *
	 * Changed in version 2.2
	 */
	FUSE_OPEN(const char *path, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    int fd;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    fd = open(fpath, fi->flags);
	    if (fd < 0)
		retstat = this->controller->error("this->open open");
	    
	    fi->fh = fd;
	    
	    return retstat;
	}
	
	/** Read data from an open file
	 *
	 * Read should return exactly the number of bytes requested except
	 * on EOF or error, otherwise the rest of the data will be
	 * substituted with zeroes.  An exception to this is when the
	 * 'direct_io' mount option is specified, in which case the return
	 * value of the read system call will reflect the return value of
	 * this operation.
	 *
	 * Changed in version 2.2
	 */
	// I don't fully understand the documentation above -- it doesn't
	// match the documentation for the read() system call which says it
	// can return with anything up to the amount of data requested. nor
	// with the fusexmp code which returns the amount of data also
	// returned by read.
	FUSE_READ(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	    // no need to get fpath on this one, since I work from fi->fh not the path
	    
	    retstat = pread(fi->fh, buf, size, offset);
	    if (retstat < 0)
		retstat = this->controller->error("this->read read");
	    
	    return retstat;
	}
	
	/** Write data to an open file
	 *
	 * Write should return exactly the number of bytes requested
	 * except on error.  An exception to this is when the 'direct_io'
	 * mount option is specified (see read operation).
	 *
	 * Changed in version 2.2
	 */
	// As  with read(), the documentation above is inconsistent with the
	// documentation for the write() system call.
	FUSE_WRITE(const char *path, const char *buf, size_t size, off_t offset,
		     struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	    // no need to get fpath on this one, since I work from fi->fh not the path
		
	    retstat = pwrite(fi->fh, buf, size, offset);
	    if (retstat < 0)
		retstat = this->controller->error("this->write pwrite");
	    
	    return retstat;
	}
	
	/** Get file system statistics
	 *
	 * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored
	 *
	 * Replaced 'struct statfs' parameter with 'struct statvfs' in
	 * version 2.5
	 */
	FUSE_STATFS(const char *path, struct statvfs *statv)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    // get stats for underlying filesystem
	    retstat = statvfs(fpath, statv);
	    if (retstat < 0)
		retstat = this->controller->error("this->statfs statvfs");
	    
	    
	    return retstat;
	}
	
	/** Possibly flush cached data
	 *
	 * BIG NOTE: This is not equivalent to fsync().  It's not a
	 * request to sync dirty data.
	 *
	 * Flush is called on each close() of a file descriptor.  So if a
	 * filesystem wants to return write errors in close() and the file
	 * has cached dirty data, this is a good place to write back data
	 * and return any errors.  Since many applications ignore close()
	 * errors this is not always useful.
	 *
	 * NOTE: The flush() method may be called more than once for each
	 * open().  This happens if more than one file descriptor refers
	 * to an opened file due to dup(), dup2() or fork() calls.  It is
	 * not possible to determine if a flush is final, so each flush
	 * should be treated equally.  Multiple write-flush sequences are
	 * relatively rare, so this shouldn't be a problem.
	 *
	 * Filesystems shouldn't assume that flush will always be called
	 * after some writes, or that if will be called at all.
	 *
	 * Changed in version 2.2
	 */
	FUSE_FLUSH(const char *path, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	    // no need to get fpath on this one, since I work from fi->fh not the path
		
	    return retstat;
	}
	
	/** Release an open file
	 *
	 * Release is called when there are no more references to an open
	 * file: all file descriptors are closed and all memory mappings
	 * are unmapped.
	 *
	 * For every open() call there will be exactly one release() call
	 * with the same flags and file descriptor.  It is possible to
	 * have a file opened more than once, in which case only the last
	 * release will mean, that no more reads/writes will happen on the
	 * file.  The return value of release is ignored.
	 *
	 * Changed in version 2.2
	 */
	FUSE_RELEASE(const char *path, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	
	    // We need to close the file.  Had we allocated any resources
	    // (buffers etc) we'd need to free them here as well.
	    retstat = close(fi->fh);
	    
	    return retstat;
	}
	
	/** Synchronize file contents
	 *
	 * If the datasync parameter is non-zero, then only the user data
	 * should be flushed, not the meta data.
	 *
	 * Changed in version 2.2
	 */
	FUSE_FSYNC(const char *path, int datasync, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	    
	    if (datasync)
		retstat = fdatasync(fi->fh);
	    else
		retstat = fsync(fi->fh);
	    
	    if (retstat < 0)
		this->controller->error("this->fsync fsync");
	    
	    return retstat;
	}
	
	/** Set extended attributes */
	FUSE_SETXATTR(const char *path, const char *name, const char *value, size_t size, int flags)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = lsetxattr(fpath, name, value, size, flags);
	    if (retstat < 0)
		retstat = this->controller->error("this->setxattr lsetxattr");
	    
	    return -retstat;
	}
	
	/** Get extended attributes */
	FUSE_GETXATTR(const char *path, const char *name, char *value, size_t size)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = lgetxattr(fpath, name, value, size);
	    if (retstat < 0) {
		retstat = this->controller->error("this->getxattr lgetxattr");
	    }
	    else
	    
	    // memset(value, 0, size);
	    return 0;
	}
	
	/** List extended attributes */
	FUSE_LISTXATTR(const char *path, char *list, size_t size)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    char *ptr;
	    
	    this->fullpath(fpath, path);
	    
	    retstat = llistxattr(fpath, list, size);
	    if (retstat < 0)
		retstat = this->controller->error("this->listxattr llistxattr");
	    
	    for (ptr = list; ptr < list + retstat; ptr += strlen(ptr)+1)
	    
	    return retstat;
	}
	
	/** Remove extended attributes */
	FUSE_REMOVEXATTR(const char *path, const char *name)
	{
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    retstat = lremovexattr(fpath, name);
	    if (retstat < 0)
		retstat = this->controller->error("this->removexattr lrmovexattr");
	    
	    return retstat;
	}
	
	/** Open directory
	 *
	 * This method should check if the open operation is permitted for
	 * this  directory
	 *
	 * Introduced in version 2.3
	 */
	FUSE_OPENDIR(const char *path, struct fuse_file_info *fi)
	{
	    DIR *dp;
	    int retstat = 0;
	    char fpath[PATH_MAX];
	    
	    this->fullpath(fpath, path);
	    
	    dp = opendir(fpath);
	    if (dp == NULL)
		retstat = this->controller->error("this->opendir opendir");
	    
	    fi->fh = (intptr_t) dp;
	    
	    
	    return retstat;
	}
	
	/** Read directory
	 *
	 * This supersedes the old getdir() interface.  New applications
	 * should use this.
	 *
	 * The filesystem may choose between two modes of operation:
	 *
	 * 1) The readdir implementation ignores the offset parameter, and
	 * passes zero to the filler function's offset.  The filler
	 * function will not return '1' (unless an error happens), so the
	 * whole directory is read in a single readdir operation.  This
	 * works just like the old getdir() method.
	 *
	 * 2) The readdir implementation keeps track of the offsets of the
	 * directory entries.  It uses the offset parameter and always
	 * passes non-zero offset to the filler function.  When the buffer
	 * is full (or an error happens) the filler function will return
	 * '1'.
	 *
	 * Introduced in version 2.3
	 */
	FUSE_READDIR(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
		       struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    DIR *dp;
	    struct dirent *de;
	    
	    // once again, no need for fullpath -- but note that I need to cast fi->fh
	    dp = (DIR *) (uintptr_t) fi->fh;
	
	    // Every directory contains at least two entries: . and ..  If my
	    // first call to the system readdir() returns NULL I've got an
	    // error; near as I can tell, that's the only condition under
	    // which I can get an error from readdir()
	    de = readdir(dp);
	    if (de == 0) {
		retstat = this->controller->error("this->readdir readdir");
		return retstat;
	    }
	
	    // This will copy the entire directory into the buffer.  The loop exits
	    // when either the system readdir() returns NULL, or filler()
	    // returns something non-zero.  The first case just means I've
	    // read the whole directory; the second means the buffer is full.
	    do {
		if (filler(buf, de->d_name, NULL, 0) != 0) {
		    return -ENOMEM;
		}
	    } while ((de = readdir(dp)) != NULL);
	    
	    
	    return retstat;
	}
	
	/** Release directory
	 *
	 * Introduced in version 2.3
	 */
	FUSE_RELEASEDIR(const char *path, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	    
	    closedir((DIR *) (uintptr_t) fi->fh);
	    
	    return retstat;
	}
	
	/** Synchronize directory contents
	 *
	 * If the datasync parameter is non-zero, then only the user data
	 * should be flushed, not the meta data
	 *
	 * Introduced in version 2.3
	 */
	// when exactly is this called?  when a user calls fsync and it
	// happens to be a directory? ???
	FUSE_FSYNCDIR(const char *path, int datasync, struct fuse_file_info *fi)
	{
	    int retstat = 0;
	    
	    
	    return retstat;
	}
    
    	/* Sets the mount source to work with (LocalFS-specific) */
	char *setRootDir(const char *path) {
		this->root_dir = strdup(path);
		return(this->root_dir);
	}
	private:
	char *root_dir;
	/* Relative to mount point -> source absolute path (LocalFS-specific) */
	void fullpath(char fpath[PATH_MAX], const char *path) {
		strcpy(fpath, this->root_dir);
		strncat(fpath, path, PATH_MAX); // ridiculously long paths will
		// break here
	}
};

int main(int argc, char **argv) {
	FSController fscon;
	LocalFS local;
	fscon.loadHostConfig(FZ_HOST_URI, argv[1]);
	local.setRootDir(argv[1]+7);
	argv++;
	argc--;
	int ret = fscon.main(argc, argv, &local);
	printf("FUSE returns: %d", ret);
}
